/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003-2005 University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.ba.type;

import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.LocalVariable;
import org.apache.bcel.classfile.LocalVariableTypeTable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ATHROW;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.CodeExceptionGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.ExceptionThrower;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;

import edu.umd.cs.findbugs.Analyze;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.AnalysisFeatures;
import edu.umd.cs.findbugs.ba.BasicBlock;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.Dataflow;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.DepthFirstSearch;
import edu.umd.cs.findbugs.ba.Edge;
import edu.umd.cs.findbugs.ba.EdgeTypes;
import edu.umd.cs.findbugs.ba.FrameDataflowAnalysis;
import edu.umd.cs.findbugs.ba.Hierarchy;
import edu.umd.cs.findbugs.ba.Hierarchy2;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.ObjectTypeFactory;
import edu.umd.cs.findbugs.ba.RepositoryLookupFailureCallback;
import edu.umd.cs.findbugs.ba.SignatureConverter;
import edu.umd.cs.findbugs.ba.generic.GenericObjectType;
import edu.umd.cs.findbugs.ba.generic.GenericSignatureParser;
import edu.umd.cs.findbugs.ba.generic.GenericUtilities;
import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;

/**
 * A forward dataflow analysis to determine the types of all values
 * in the Java stack frame at all points in a Java method.
 * The values include local variables and values on the Java operand stack.
 * <p/>
 * <p> As a side effect, the analysis computes the exception
 * set throwable on each exception edge in the CFG.
 * This information can be used to prune infeasible exception
 * edges, and mark exception edges which propagate only
 * implicit exceptions.
 *
 * @author David Hovemeyer
 * @see Dataflow
 * @see edu.umd.cs.findbugs.ba.DataflowAnalysis
 * @see TypeFrame
 */
public class TypeAnalysis extends FrameDataflowAnalysis<Type, TypeFrame>
		implements EdgeTypes {

	public static final boolean DEBUG = SystemProperties.getBoolean("ta.debug");

	/**
	 * Force computation of accurate exceptions.
	 */
	public static final boolean FORCE_ACCURATE_EXCEPTIONS =SystemProperties.getBoolean("ta.accurateExceptions");

	/**
	 * Repository of information about thrown exceptions computed for
	 * a basic block and its outgoing exception edges.
	 * It contains a result TypeFrame, which is used to detect
	 * when the exception information needs to be recomputed
	 * for the block.
	 */
	private class CachedExceptionSet {
		private TypeFrame result;
		private ExceptionSet exceptionSet;
		private Map<Edge, ExceptionSet> edgeExceptionMap;

		public CachedExceptionSet(TypeFrame result, ExceptionSet exceptionSet) {
			this.result = result;
			this.exceptionSet = exceptionSet;
			this.edgeExceptionMap = new HashMap<Edge, ExceptionSet>();
		}

		public boolean isUpToDate(TypeFrame result) {
			return this.result.equals(result);
		}

		public ExceptionSet getExceptionSet() {
			return exceptionSet;
		}

		public void setEdgeExceptionSet(Edge edge, ExceptionSet exceptionSet) {
			edgeExceptionMap.put(edge, exceptionSet);
		}

		public ExceptionSet getEdgeExceptionSet(Edge edge) {
			ExceptionSet edgeExceptionSet = edgeExceptionMap.get(edge);
			if (edgeExceptionSet == null) {
				edgeExceptionSet = exceptionSetFactory.createExceptionSet();
				edgeExceptionMap.put(edge, edgeExceptionSet);
			}
			return edgeExceptionSet;
		}
	}

	/**
	 * Cached information about an instanceof check.
	 */
	static class InstanceOfCheck {
		final ValueNumber valueNumber;
		final Type type;

		InstanceOfCheck(ValueNumber valueNumber, Type type) {
			this.valueNumber = valueNumber;
			this.type = type;
		}

		/**
		 * @return Returns the valueNumber.
		 */
		public ValueNumber getValueNumber() {
			return valueNumber;
		}

		/**
		 * @return Returns the type.
		 */
		public Type getType() {
			return type;
		}
	}

	protected MethodGen methodGen;
	private final Method method;
	protected CFG cfg;
	private TypeMerger typeMerger;
	private TypeFrameModelingVisitor visitor;
	private LocalVariableTypeTable typeTable;
	private BitSet startOfLocalTypedVariables = new BitSet();
	private Map<BasicBlock, CachedExceptionSet> thrownExceptionSetMap;
	private RepositoryLookupFailureCallback lookupFailureCallback;
	private ExceptionSetFactory exceptionSetFactory;
	private ValueNumberDataflow valueNumberDataflow;
	private Map<BasicBlock, InstanceOfCheck> instanceOfCheckMap;
	/**
	 * Constructor.
	 * @param method TODO
	 * @param methodGen             the MethodGen whose CFG we'll be analyzing
	 * @param cfg                   the control flow graph
	 * @param dfs                   DepthFirstSearch of the method
	 * @param typeMerger            object to merge types
	 * @param visitor               a TypeFrameModelingVisitor to use to model the effect
	 *                              of instructions
	 * @param lookupFailureCallback lookup failure callback
	 * @param exceptionSetFactory   factory for creating ExceptionSet objects
	 */
	public TypeAnalysis(Method method, MethodGen methodGen, CFG cfg,
						DepthFirstSearch dfs, TypeMerger typeMerger,
						TypeFrameModelingVisitor visitor,
						RepositoryLookupFailureCallback lookupFailureCallback, ExceptionSetFactory exceptionSetFactory) {
		super(dfs);
		this.method = method;
		Code code = method.getCode();
		if (code == null) throw new IllegalArgumentException(method.getName() + " has no code");
		for(Attribute a : code.getAttributes()) {
			if (a instanceof LocalVariableTypeTable) {
				typeTable = (LocalVariableTypeTable) a;
				for (LocalVariable v : typeTable.getLocalVariableTable()) {
					int startPC = v.getStartPC();
					if (startPC >= 0) startOfLocalTypedVariables.set(startPC);
				}
			}
		}
		this.methodGen = methodGen;
		this.cfg = cfg;
		this.typeMerger = typeMerger;
		this.visitor = visitor;
		this.thrownExceptionSetMap = new HashMap<BasicBlock, CachedExceptionSet>();
		this.lookupFailureCallback = lookupFailureCallback;
		this.exceptionSetFactory = exceptionSetFactory;
		this.instanceOfCheckMap = new HashMap<BasicBlock, InstanceOfCheck>();
		if (DEBUG) {
			System.out.println("\n\nAnalyzing " + methodGen);
		}
	}

	/**
	 * Constructor.
	 * @param method TODO
	 * @param methodGen             the MethodGen whose CFG we'll be analyzing
	 * @param cfg                   the control flow graph
	 * @param dfs                   DepthFirstSearch of the method
	 * @param typeMerger            object to merge types
	 * @param lookupFailureCallback lookup failure callback
	 * @param exceptionSetFactory   factory for creating ExceptionSet objects
	 */
	public TypeAnalysis(Method method, MethodGen methodGen, CFG cfg,
						DepthFirstSearch dfs, TypeMerger typeMerger,
						RepositoryLookupFailureCallback lookupFailureCallback, ExceptionSetFactory exceptionSetFactory) {
		this(method, methodGen, cfg, dfs,
				typeMerger, new TypeFrameModelingVisitor(methodGen.getConstantPool()),
				lookupFailureCallback, exceptionSetFactory);
	}

	/**
	 * Constructor which uses StandardTypeMerger.
	 * @param method TODO
	 * @param methodGen             the MethodGen whose CFG we'll be analyzing
	 * @param cfg                   the control flow graph
	 * @param dfs                   DepthFirstSearch of the method
	 * @param lookupFailureCallback callback for Repository lookup failures
	 * @param exceptionSetFactory   factory for creating ExceptionSet objects
	 */
	public TypeAnalysis(Method method, MethodGen methodGen, CFG cfg,
						DepthFirstSearch dfs,
						RepositoryLookupFailureCallback lookupFailureCallback, ExceptionSetFactory exceptionSetFactory) {
		this(method, methodGen, cfg, dfs,
				new StandardTypeMerger(lookupFailureCallback, exceptionSetFactory),
				lookupFailureCallback, exceptionSetFactory);
	}

	/**
	 * Set the ValueNumberDataflow for the method being analyzed.
	 * This is optional; if set, it will be used to make instanceof
	 * instructions more precise.
	 * 
	 * @param valueNumberDataflow the ValueNumberDataflow
	 */
	public void setValueNumberDataflow(ValueNumberDataflow valueNumberDataflow) {
		this.valueNumberDataflow = valueNumberDataflow;
		this.visitor.setValueNumberDataflow(valueNumberDataflow);
	}

	/**
	 * Set the FieldStoreTypeDatabase.
	 * This can be used to get more accurate types for values loaded
	 * from fields.
	 * 
	 * @param database the FieldStoreTypeDatabase 
	 */
	public void setFieldStoreTypeDatabase(FieldStoreTypeDatabase database) {
		visitor.setFieldStoreTypeDatabase(database);
	}

	/**
	 * Get the set of exceptions that can be thrown on given edge.
	 * This should only be called after the analysis completes.
	 *
	 * @param edge the Edge
	 * @return the ExceptionSet
	 */
	public ExceptionSet getEdgeExceptionSet(Edge edge) {
		CachedExceptionSet cachedExceptionSet = thrownExceptionSetMap.get(edge.getSource());
		return cachedExceptionSet.getEdgeExceptionSet(edge);
	}

	public TypeFrame createFact() {
		return new TypeFrame(methodGen.getMaxLocals());
	}

	public void initEntryFact(TypeFrame result) {
		// Make the frame valid
		result.setValid();

		int slot = 0;

		// Clear the stack slots in the frame
		result.clearStack();

		// Add local for "this" pointer, if present
		if (!methodGen.isStatic())
			result.setValue(slot++, ObjectTypeFactory.getInstance(methodGen.getClassName()));

		// [Added: Support for Generics]
		// Get a parser that reads the generic signature of the method and
		// can be used to get the correct GenericObjectType if an argument
		// has a class type
		Iterator<String> iter = 
			GenericSignatureParser.getGenericSignatureIterator(method);

		// Add locals for parameters.
		// Note that long and double parameters need to be handled
		// specially because they occupy two locals.
		Type[] argumentTypes = methodGen.getArgumentTypes();		
		for (Type argType : argumentTypes) {
			// Add special "extra" type for long or double params.
			// These occupy the slot before the "plain" type.
			if (argType.getType() == Constants.T_LONG) {
				result.setValue(slot++, TypeFrame.getLongExtraType());
			} else if (argType.getType() == Constants.T_DOUBLE) {
				result.setValue(slot++, TypeFrame.getDoubleExtraType());
			}

			// [Added: Support for Generics]
			String s = ( iter == null  || !iter.hasNext() )? null : iter.next();
			if (	s != null && 
					(argType instanceof ObjectType || argType instanceof ArrayType) &&
					!(argType instanceof ExceptionObjectType) 
				) {
				// replace with a generic version of the type
				try { 
					argType = GenericUtilities.getType(s);
				} catch (RuntimeException e) {} // degrade gracefully
			}

			// Add the plain parameter type.
			result.setValue(slot++, argType);
		}

		// Set remaining locals to BOTTOM; this will cause any
		// uses of them to be flagged
		while (slot < methodGen.getMaxLocals())
			result.setValue(slot++, TypeFrame.getBottomType());
	}

	@Override
	public void copy(TypeFrame source, TypeFrame dest) {
		dest.copyFrom(source);
	}

	@Override
	public void makeFactTop(TypeFrame fact) {
		fact.setTop();
	}

	@Override
	public boolean isFactValid(TypeFrame fact) {
		return fact.isValid();
	}

	@Override
	public boolean same(TypeFrame fact1, TypeFrame fact2) {
		return fact1.sameAs(fact2);
	}

	@Override
	public void transferInstruction(InstructionHandle handle, BasicBlock basicBlock, TypeFrame fact)
			throws DataflowAnalysisException {
		if (typeTable != null) {
			int pos = handle.getPosition();
			if (pos >= 0 && startOfLocalTypedVariables.get(pos))
			for(LocalVariable local : typeTable.getLocalVariableTable()) {
				if (local.getStartPC() == pos) {
					String signature = local.getSignature();
					Type t;
					try {
					 t = GenericUtilities.getType(signature);
					} catch (RuntimeException e) {
						AnalysisContext.logError("Bad signature " + signature + " for " + local.getName() + " in " 
								+  methodGen.getClassName() + "." + method.getName(), e);
						continue;
					}
					if (t instanceof GenericObjectType) {
						int index = local.getIndex();
						Type currentValue = fact.getValue(index);
						if (!(currentValue instanceof GenericObjectType) && (currentValue instanceof ObjectType))
							fact.setValue(index, GenericUtilities.merge((GenericObjectType)t, (ObjectType)currentValue));
					}

				}
			}
		}
		visitor.setFrameAndLocation(fact, new Location(handle, basicBlock));
		visitor.analyzeInstruction(handle.getInstruction());
	}

	/* (non-Javadoc)
	 * @see edu.umd.cs.findbugs.ba.AbstractDataflowAnalysis#transfer(edu.umd.cs.findbugs.ba.BasicBlock, org.apache.bcel.generic.InstructionHandle, java.lang.Object, java.lang.Object)
	 */
	@Override
	public void transfer(BasicBlock basicBlock, @CheckForNull InstructionHandle end, TypeFrame start, TypeFrame result) throws DataflowAnalysisException {
		visitor.startBasicBlock();

		super.transfer(basicBlock, end, start, result);

		// Compute thrown exception types
		computeThrownExceptionTypes(basicBlock, end, result);
		if (DEBUG) {
			System.out.println("After " + basicBlock.getFirstInstruction() + " -> " + basicBlock.getLastInstruction());
			System.out.println("    frame: " + result);
		}

		// If this block ends with an instanceof check,
		// update the cached information about it.
		instanceOfCheckMap.remove(basicBlock);
		if (visitor.isInstanceOfFollowedByBranch()) {
			InstanceOfCheck check = new InstanceOfCheck(visitor.getInstanceOfValueNumber(), visitor.getInstanceOfType());
			instanceOfCheckMap.put(basicBlock, check);
		}
	}

	private void computeThrownExceptionTypes(BasicBlock basicBlock, @CheckForNull InstructionHandle end, TypeFrame result)
			throws DataflowAnalysisException {

		// Do nothing if we're not computing propagated exceptions
		if (!(FORCE_ACCURATE_EXCEPTIONS ||
				AnalysisContext.currentAnalysisContext().getBoolProperty(AnalysisFeatures.ACCURATE_EXCEPTIONS)))
			return;

		// Also, nothing to do if the block is not an exception thrower
		if (!basicBlock.isExceptionThrower())
			return;

		// If cached results are up to date, don't recompute.
		CachedExceptionSet cachedExceptionSet = getCachedExceptionSet(basicBlock);
		if (cachedExceptionSet.isUpToDate(result))
			return;

		// Figure out what exceptions can be thrown out
		// of the basic block, and mark each exception edge
		// with the set of exceptions which can be propagated
		// along the edge.

		int exceptionEdgeCount = 0;
		Edge lastExceptionEdge = null;

		for (Iterator<Edge> i = cfg.outgoingEdgeIterator(basicBlock); i.hasNext();) {
			Edge e = i.next();
			if (e.isExceptionEdge()) {
				exceptionEdgeCount++;
				lastExceptionEdge = e;
			}
		}

		if (exceptionEdgeCount == 0) {
			// System.out.println("Shouldn't all blocks have an exception edge");
			return;
		}
		// Compute exceptions that can be thrown by the
		// basic block.
		cachedExceptionSet = computeBlockExceptionSet(basicBlock, result);

		if (exceptionEdgeCount == 1) {
			cachedExceptionSet.setEdgeExceptionSet(lastExceptionEdge, cachedExceptionSet.getExceptionSet());
			return;
		}


		// For each outgoing exception edge, compute exceptions
		// that can be thrown.  This assumes that the exception
		// edges are enumerated in decreasing order of priority.
		// In the process, this will remove exceptions from
		// the thrown exception set.
		ExceptionSet thrownExceptionSet = cachedExceptionSet.getExceptionSet();
		if (!thrownExceptionSet.isEmpty()) thrownExceptionSet = thrownExceptionSet.duplicate();
		for (Iterator<Edge> i = cfg.outgoingEdgeIterator(basicBlock); i.hasNext();) {
			Edge edge = i.next();
			if (edge.isExceptionEdge())
				cachedExceptionSet.setEdgeExceptionSet(edge, computeEdgeExceptionSet(edge, thrownExceptionSet));
		}
	}

	public void meetInto(TypeFrame fact, Edge edge, TypeFrame result) throws DataflowAnalysisException {
		BasicBlock basicBlock = edge.getTarget();

		if (fact.isValid()) {
			TypeFrame tmpFact = null;

			// Handling an exception?
			if (basicBlock.isExceptionHandler()) {
				tmpFact = modifyFrame(fact, tmpFact);

				// Special case: when merging predecessor facts for entry to
				// an exception handler, we clear the stack and push a
				// single entry for the exception object.  That way, the locals
				// can still be merged.
				CodeExceptionGen exceptionGen = basicBlock.getExceptionGen();
				tmpFact.clearStack();

				// Determine the type of exception(s) caught.
				Type catchType = null;

				if (FORCE_ACCURATE_EXCEPTIONS ||
						AnalysisContext.currentAnalysisContext().getBoolProperty(AnalysisFeatures.ACCURATE_EXCEPTIONS)) {
					try {
						// Ideally, the exceptions that can be propagated
						// on this edge has already been computed.
						CachedExceptionSet cachedExceptionSet = getCachedExceptionSet(edge.getSource());
						ExceptionSet edgeExceptionSet = cachedExceptionSet.getEdgeExceptionSet(edge);
						if (!edgeExceptionSet.isEmpty()) {
							//System.out.println("Using computed edge exception set!");
							catchType = ExceptionObjectType.fromExceptionSet(edgeExceptionSet);
						}
					} catch (ClassNotFoundException e) {
						lookupFailureCallback.reportMissingClass(e);
					}
				}

				if (catchType == null) {
					// No information about propagated exceptions, so
					// pick a type conservatively using the handler catch type.
					catchType = exceptionGen.getCatchType();
					if (catchType == null)
						catchType = Type.THROWABLE; // handle catches anything throwable
				}

				tmpFact.pushValue(catchType);
			}

			// See if we can make some types more precise due to
			// a successful instanceof check in the source block.
			if (valueNumberDataflow != null) {
				tmpFact = handleInstanceOfBranch(fact, tmpFact, edge);
			}

			if (tmpFact != null) {
				fact = tmpFact;
			}
		}

		mergeInto(fact, result);
	}

	private TypeFrame handleInstanceOfBranch(TypeFrame fact, TypeFrame tmpFact, Edge edge) throws DataflowAnalysisException {

		InstanceOfCheck check = instanceOfCheckMap.get(edge.getSource());
		if (check == null) {
			//System.out.println("no instanceof check for block " + edge.getSource().getId());
			return tmpFact;
		}

		if (check.getValueNumber() == null) {
			//System.out.println("instanceof check for block " + edge.getSource().getId() + " has no value number");
			return tmpFact;
		}

		ValueNumber instanceOfValueNumber = check.getValueNumber();
		ValueNumberFrame vnaFrame = valueNumberDataflow.getStartFact(edge.getTarget());
		if (!vnaFrame.isValid())
			return tmpFact;

		Type instanceOfType = check.getType();
		if (!(instanceOfType instanceof ReferenceType || instanceOfType instanceof NullType))
			return tmpFact;

		short branchOpcode = edge.getSource().getLastInstruction().getInstruction().getOpcode();

		int edgeType = edge.getType();
		int numSlots = Math.min(fact.getNumSlots(), vnaFrame.getNumSlots());
		
		if (    (edgeType == EdgeTypes.IFCMP_EDGE &&
						(branchOpcode == Constants.IFNE || branchOpcode == Constants.IFGT || branchOpcode == Constants.IFNULL))

			|| (edgeType == EdgeTypes.FALL_THROUGH_EDGE &&
						(branchOpcode == Constants.IFEQ || branchOpcode == Constants.IFLE || branchOpcode == Constants.IFNONNULL))
		) {
			//System.out.println("Successful check on edge " + edge);

			// Successful instanceof check.
		
			for (int i = 0; i < numSlots; ++i) {
				if (!vnaFrame.getValue(i).equals(instanceOfValueNumber))
					continue;

				Type checkedType = fact.getValue(i);
				if (!(checkedType instanceof ReferenceType))
					continue;


				// Only refine the type if the cast is feasible: i.e., a downcast.
				// Otherwise, just set it to TOP.
				try {
					boolean guaranteed = Hierarchy.isSubtype(
								(ReferenceType) checkedType,
								(ReferenceType) instanceOfType);
					if (guaranteed) continue;
					
					boolean feasibleCheck = instanceOfType.equals(NullType.instance()) 
						|| Hierarchy.isSubtype(
							(ReferenceType) instanceOfType,
							(ReferenceType) checkedType);
					
					if (!feasibleCheck && instanceOfType instanceof ObjectType 
							&& checkedType instanceof ObjectType) {
						double v = Analyze.deepInstanceOf(((ObjectType)instanceOfType).getClassName(), ((ObjectType)checkedType).getClassName());
						if (v > 0.0) feasibleCheck = true;
					}
					tmpFact = modifyFrame(fact, tmpFact);
					if (feasibleCheck) {
						tmpFact.setValue(i,  instanceOfType);
					} else {
						tmpFact.setTop();
						return tmpFact;
					}
				} catch (ClassNotFoundException e) {
					lookupFailureCallback.reportMissingClass(e);
					return tmpFact;
				}
			}
		} else if (!instanceOfType.equals(NullType.instance())) {
			
			for (int i = 0; i < numSlots; ++i) {
				if (!vnaFrame.getValue(i).equals(instanceOfValueNumber))
					continue;

				Type checkedType = fact.getValue(i);
				if (!(checkedType instanceof ReferenceType))
					continue;
				try {
					boolean guaranteed =  Hierarchy.isSubtype(
							(ReferenceType) checkedType,
							(ReferenceType) instanceOfType);
					if (!guaranteed) continue;
					tmpFact = modifyFrame(fact, tmpFact);
					tmpFact.setTop();
					return tmpFact;
				} catch (ClassNotFoundException e) {
					lookupFailureCallback.reportMissingClass(e);
					return tmpFact;
				}
			}
		}

		return tmpFact;
	}

	@Override
	protected void mergeValues(TypeFrame otherFrame, TypeFrame resultFrame, int slot) throws DataflowAnalysisException {
	
		Type type2 = resultFrame.getValue(slot);
		Type type1 = otherFrame.getValue(slot);
		Type value = typeMerger.mergeTypes(type2, type1);
		resultFrame.setValue(slot, value);

		// Result type is exact IFF types are identical and both are exact

		boolean typesAreIdentical =
			type1.equals(type2);

		boolean bothExact =
			resultFrame.isExact(slot) && otherFrame.isExact(slot);

		resultFrame.setExact(slot, typesAreIdentical && bothExact);
	}

	/**
	 * Get the cached set of exceptions that can be thrown
	 * from given basic block.  If this information hasn't
	 * been computed yet, then an empty exception set is
	 * returned.
	 *
	 * @param basicBlock the block to get the cached exception set for
	 * @return the CachedExceptionSet for the block
	 */
	private CachedExceptionSet getCachedExceptionSet(BasicBlock basicBlock) {
		CachedExceptionSet cachedExceptionSet = thrownExceptionSetMap.get(basicBlock);
		if (cachedExceptionSet == null) {
			// When creating the cached exception type set for the first time:
			// - the block result is set to TOP, so it won't match
			//   any block result that has actually been computed
			//   using the analysis transfer function
			// - the exception set is created as empty (which makes it
			//   return TOP as its common superclass)

			TypeFrame top = createFact();
			makeFactTop(top);
			cachedExceptionSet = new CachedExceptionSet(top, exceptionSetFactory.createExceptionSet());

			thrownExceptionSetMap.put(basicBlock, cachedExceptionSet);
		}

		return cachedExceptionSet;
	}

	/**
	 * Compute the set of exceptions that can be
	 * thrown from the given basic block.
	 * This should only be called if the existing cached
	 * exception set is out of date.
	 *
	 * @param basicBlock the basic block
	 * @param result     the result fact for the block; this is used
	 *                   to determine whether or not the cached exception
	 *                   set is up to date
	 * @return the cached exception set for the block
	 */
	private CachedExceptionSet computeBlockExceptionSet(BasicBlock basicBlock, TypeFrame result)
			throws DataflowAnalysisException {

		ExceptionSet exceptionSet;
		try {
			exceptionSet = computeThrownExceptionTypes(basicBlock);
		} catch (ClassNotFoundException e) {
			// Special case: be as conservative as possible
			// if a class hierarchy lookup fails.
			lookupFailureCallback.reportMissingClass(e);
			exceptionSet = exceptionSetFactory.createExceptionSet();
			exceptionSet.addExplicit(Type.THROWABLE);
		}

		TypeFrame copyOfResult = createFact();
		copy(result, copyOfResult);

		CachedExceptionSet cachedExceptionSet = new CachedExceptionSet(copyOfResult, exceptionSet);
		thrownExceptionSetMap.put(basicBlock, cachedExceptionSet);

		return cachedExceptionSet;
	}

	/**
	 * Based on the set of exceptions that can be thrown
	 * from the source basic block,
	 * compute the set of exceptions that can propagate
	 * along given exception edge.  This method should be
	 * called for each outgoing exception edge in sequence,
	 * so the caught exceptions can be removed from the
	 * thrown exception set as needed.
	 *
	 * @param edge               the exception edge
	 * @param thrownExceptionSet current set of exceptions that
	 *                           can be thrown, taking earlier (higher priority)
	 *                           exception edges into account
	 * @return the set of exceptions that can propagate
	 *         along this edge
	 */
	private ExceptionSet computeEdgeExceptionSet(Edge edge, ExceptionSet thrownExceptionSet) {


		if (thrownExceptionSet.isEmpty()) return thrownExceptionSet;
		ExceptionSet result = exceptionSetFactory.createExceptionSet();

		if (edge.getType() == UNHANDLED_EXCEPTION_EDGE) {
			// The unhandled exception edge always comes
			// after all of the handled exception edges.
			result.addAll(thrownExceptionSet);
			thrownExceptionSet.clear();
			return result;
		}

		BasicBlock handlerBlock = edge.getTarget();
		CodeExceptionGen handler = handlerBlock.getExceptionGen();
		ObjectType catchType = handler.getCatchType();

		if (Hierarchy.isUniversalExceptionHandler(catchType)) {
			result.addAll(thrownExceptionSet);
			thrownExceptionSet.clear();
		} else {
			// Go through the set of thrown exceptions.
			// Any that will DEFINITELY be caught be this handler, remove.
			// Any that MIGHT be caught, but won't definitely be caught,
			// remain.

			for (ExceptionSet.ThrownExceptionIterator i = thrownExceptionSet.iterator(); i.hasNext();) {
				//ThrownException thrownException = i.next();
				ObjectType thrownType = i.next();
				boolean explicit = i.isExplicit();

				if (DEBUG)
					System.out.println("\texception type " + thrownType +
							", catch type " + catchType);

				try {
					if (Hierarchy.isSubtype(thrownType, catchType)) {
						// Exception can be thrown along this edge
						result.add(thrownType, explicit);

						// And it will definitely be caught
						i.remove();

						if (DEBUG)
							System.out.println("\tException is subtype of catch type: " +
									"will definitely catch");
					} else if (Hierarchy.isSubtype(catchType, thrownType)) {
						// Exception possibly thrown along this edge
						result.add(thrownType, explicit);

						if (DEBUG)
							System.out.println("\tException is supertype of catch type: " +
									"might catch");
					}
				} catch (ClassNotFoundException e) {
					// As a special case, if a class hierarchy lookup
					// fails, then we will conservatively assume that the
					// exception in question CAN, but WON'T NECESSARILY
					// be caught by the handler.
					AnalysisContext.reportMissingClass(e);
					result.add(thrownType, explicit);
				}
			}
		}

		return result;
	}

	/**
	 * Compute the set of exception types that can
	 * be thrown by given basic block.
	 *
	 * @param basicBlock the basic block
	 * @return the set of exceptions that can be thrown by the block
	 */
	private ExceptionSet computeThrownExceptionTypes(BasicBlock basicBlock)
			throws ClassNotFoundException, DataflowAnalysisException {

		ExceptionSet exceptionTypeSet = exceptionSetFactory.createExceptionSet();
		InstructionHandle pei = basicBlock.getExceptionThrower();
		Instruction ins = pei.getInstruction();

		// Get the exceptions that BCEL knows about.
		// Note that all of these are unchecked.
		ExceptionThrower exceptionThrower = (ExceptionThrower) ins;
		Class<?>[] exceptionList = exceptionThrower.getExceptions();
		for (Class<?> aExceptionList : exceptionList) {
			exceptionTypeSet.addImplicit(ObjectTypeFactory.getInstance(aExceptionList.getName()));
		}

		// Assume that an Error may be thrown by any instruction.
		exceptionTypeSet.addImplicit(Hierarchy.ERROR_TYPE);

		if (ins instanceof ATHROW) {
			// For ATHROW instructions, we generate *two* blocks
			// for which the ATHROW is an exception thrower.
			//
			// - The first, empty basic block, does the null check
			// - The second block, which actually contains the ATHROW,
			//   throws the object on the top of the operand stack
			//
			// We make a special case of the block containing the ATHROW,
			// by removing all of the implicit exceptions,
			// and using type information to figure out what is thrown.

			if (basicBlock.containsInstruction(pei)) {
				// This is the actual ATHROW, not the null check
				// and implicit exceptions.
				exceptionTypeSet.clear();

				// The frame containing the thrown value is the start fact
				// for the block, because ATHROW is guaranteed to be
				// the only instruction in the block.
				TypeFrame frame = getStartFact(basicBlock);

				// Check whether or not the frame is valid.
				// Sun's javac sometimes emits unreachable code.
				// For example, it will emit code that follows a JSR
				// subroutine call that never returns.
				// If the frame is invalid, then we can just make
				// a conservative assumption that anything could be
				// thrown at this ATHROW.
				if (!frame.isValid()) {
					exceptionTypeSet.addExplicit(Type.THROWABLE);
				} else if (frame.getStackDepth() == 0) {
					throw new IllegalStateException("empty stack " +
							" thrown by " + pei + " in " +
							SignatureConverter.convertMethodSignature(methodGen));
				} else {

					Type throwType = frame.getTopValue();
					if (throwType instanceof ObjectType) {
						exceptionTypeSet.addExplicit((ObjectType) throwType);
					} else if (throwType instanceof ExceptionObjectType) {
						exceptionTypeSet.addAll(((ExceptionObjectType) throwType).getExceptionSet());
					} else {
						// Not sure what is being thrown here.
						// Be conservative.
						if (DEBUG) {
							System.out.println("Non object type " + throwType +
									" thrown by " + pei + " in " +
									SignatureConverter.convertMethodSignature(methodGen));
						}
						exceptionTypeSet.addExplicit(Type.THROWABLE);
					}
				}
			}
		}

		// If it's an InvokeInstruction, add declared exceptions and RuntimeException
		if (ins instanceof InvokeInstruction) {
			ConstantPoolGen cpg = methodGen.getConstantPool();

			InvokeInstruction inv = (InvokeInstruction) ins;
			ObjectType[] declaredExceptionList = Hierarchy2.findDeclaredExceptions(inv, cpg);
			if (declaredExceptionList == null) {
				// Couldn't find declared exceptions,
				// so conservatively assume it could thrown any checked exception.
				if (DEBUG)
					System.out.println("Couldn't find declared exceptions for " +
							SignatureConverter.convertMethodSignature(inv, cpg));
				exceptionTypeSet.addExplicit(Hierarchy.EXCEPTION_TYPE);
			} else {
				for (ObjectType aDeclaredExceptionList : declaredExceptionList) {
					exceptionTypeSet.addExplicit(aDeclaredExceptionList);
				}
			}

			exceptionTypeSet.addImplicit(Hierarchy.RUNTIME_EXCEPTION_TYPE);
		}

		if (DEBUG) System.out.println(pei + " can throw " + exceptionTypeSet);

		return exceptionTypeSet;
	}
	
	@Override
	public String toString() {
		return this.getClass().getSimpleName() + "(" + methodGen.getClassName() + "." + methodGen.getMethod().getName()
					+ methodGen.getMethod().getSignature() + ")";
	}

	public boolean isImpliedByGenericTypes(ReferenceType t) {
		return visitor.isImpliedByGenericTypes(t);
	}
	
//	public static void main(String[] argv) throws Exception {
//		if (argv.length != 1) {
//			System.err.println("Usage: " + TypeAnalysis.class.getName() + " <class file>");
//			System.exit(1);
//		}
//
//		DataflowTestDriver<TypeFrame, TypeAnalysis> driver = new DataflowTestDriver<TypeFrame, TypeAnalysis>() {
//			@Override
//			public Dataflow<TypeFrame, TypeAnalysis> createDataflow(ClassContext classContext, Method method)
//					throws CFGBuilderException, DataflowAnalysisException {
//				return classContext.getTypeDataflow(method);
//			}
//		};
//
//		driver.execute(argv[0]);
//	}
}

// vim:ts=3
